using Newtonsoft.Json.Linq;
using AggDataDic = System.Collections.Immutable.ImmutableSortedDictionary<string, object>;

namespace my_console5;

public class AggOutput
{
    public static List<AggDataDic> Parse(JToken token)
    {
        var dataList = new List<AggDataDic>();
        Parse(dataList, AggDataDic.Empty, token);

        return dataList;
    }

    static void Parse(List<AggDataDic> dataList, AggDataDic data, JToken jtoken)
    {
        if (tryParseBuckets(dataList, data, jtoken))
        {
            return;
        }

        if (tryParseMetrics(dataList, data, jtoken))
        {
            return;
        }

        if (tryParseNested(dataList, data, jtoken))
        {
            return;
        }
    }

    /// <summary>
    /// buckets类型
    /// </summary>
    static bool tryParseBuckets(List<AggDataDic> dataList, AggDataDic data, JToken jtoken)
    {
        var buckets = iterateToken(jtoken, "buckets").FirstOrDefault();
        if (buckets == null)
        {
            return false;
        }

        var bucketTokens = buckets.value switch
        {
            JArray array => iterateArrayBucket(buckets.field, array),
            JObject obj => iterateObjBucket(buckets.field, obj),
            _ => throw new InvalidDataException(),
        };

        foreach (var token in bucketTokens)
        {
            Parse(dataList, data.Add(token.field, token.value), token.children);
        }

        return true;
    }

    /// <summary>
    /// metrics类型
    /// </summary>
    static bool tryParseMetrics(List<AggDataDic> dataList, AggDataDic data, JToken jtoken)
    {
        //先分组，再分别求数量的场景
        var metrics1 = jtoken
            .Children<JProperty>()
            .Select(i => i.Value)
            .OfType<JObject>()
            .SelectMany(i => iterateToken(i, "value"))
            .Concat(iterateToken(jtoken, "value"));

        //直接分组
        var metrics2 = iterateToken(jtoken, "value");
        var metrics = metrics1.Concat(metrics2).ToArray();

        if (metrics.Length == 0)
        {
            return false;
        }

        foreach (var metric in metrics)
        {
            var value = (metric.value as JValue).Value;
            data = data.Add(metric.field, value);
        }

        dataList.Add(data);
        return true;
    }

    /// <summary>
    /// nested类型 主要是检查类型相关分组
    /// </summary>
    static bool tryParseNested(List<AggDataDic> dataList, AggDataDic data, JToken jtoken)
    {
        //自定义的标志，只有nested聚合类型有

        var child = jtoken.Children<JProperty>().Select(i => i.Value).FirstOrDefault(isValidToken);

        if (child == null)
            return false;

        Parse(dataList, data, child);
        return true;

        static bool isValidToken(JToken child)
        {
            return child?.SelectToken("meta.opType")?.Value<string>() == "nested";
        }
    }

    /// <summary>
    /// 迭代出聚合字段 (bucket和metric)
    /// </summary>
    static IEnumerable<AggToken> iterateToken(JToken obj, string field)
    {
        foreach (var property in obj.Children<JProperty>())
        {
            var pValue = property.Value;

            if (pValue is not JObject)
                continue;

            var value = pValue[field];
            if (value == null)
                continue;

            yield return new AggToken(property.Name, value);
        }
    }

    static IEnumerable<AggBucketToken> iterateArrayBucket(string field, JArray array)
    {
        foreach (var obj in array.Children<JObject>())
        {
            yield return new AggBucketToken(field, getBucketKey(obj, field), obj);
        }
    }

    static string getBucketKey(JObject obj, string field)
    {
        var keyAsStr = obj.Value<JValue>("key_as_string");
        if (keyAsStr == null)
        {
            return obj.Value<string>("key");
        }

        if (keyAsStr.Value is DateTime time)
        {
            //对于时间类型的key要转换为响应的字符串（默认无法区分年月日）
            if (field.Contains("年"))
                return time.ToString("yyyy");
            if (field.Contains("月"))
                return time.ToString("yyyy-M");
            if (field.Contains("日"))
                return time.ToString("yyyy-M-d");
        }

        return keyAsStr.Value<string>();
    }

    static IEnumerable<AggBucketToken> iterateObjBucket(string field, JObject obj)
    {
        foreach (var property in obj.Children<JProperty>())
        {
            var key = property.Name;
            if (property.Value is not JObject)
                continue;

            yield return new AggBucketToken(field, key, property.Value);
        }
    }

    record AggToken(string field, object value);

    record AggBucketToken(string field, object value, JToken children);
}
